package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/xuri/excelize/v2"
)

var (
	mimeToExt = map[string]string{
		"image/jpeg":      ".jpg",
		"image/png":       ".png",
		"image/gif":       ".gif",
		"application/pdf": ".pdf",
	}
	successCount int64 // 原子计数器用于下载成功数
)

const (
	maxConcurrency = 10               // 最大并发数
	requestTimeout = 30 * time.Second // 请求超时时间
)

func main() {
	startTime := time.Now()
	inputFile := "./采购订单单号-0324--2.xlsx"
	outputDir := "./outfile2"

	f, err := excelize.OpenFile(inputFile)
	if err != nil {
		panic(fmt.Sprintf("打开Excel文件失败: %v", err))
	}
	defer f.Close()

	rows, err := f.GetRows("Sheet1")
	if err != nil {
		panic(fmt.Sprintf("读取工作表失败: %v", err))
	}

	// 预创建所有需要的目录
	if err := preCreateDirs(rows, outputDir); err != nil {
		panic(err)
	}

	// 创建带缓冲的通道实现工作池
	rowChan := make(chan []string, len(rows))
	var wg sync.WaitGroup
	wg.Add(maxConcurrency)

	// 启动工作者协程
	for i := 0; i < maxConcurrency; i++ {
		go worker(i, &wg, rowChan, outputDir)
	}

	// 分发任务（跳过标题行）
	for i, row := range rows {
		if i == 0 || len(row) < 3 {
			continue
		}
		rowChan <- row
	}

	close(rowChan)
	wg.Wait()

	// 输出统计信息
	totalTime := time.Since(startTime)
	fmt.Printf("\n统计结果:")
	fmt.Printf("\n%-20s %d", "Excel数据总行数:", len(rows)-1)
	fmt.Printf("\n%-20s %d", "文件下载成功数:", atomic.LoadInt64(&successCount))
	fmt.Printf("\n%-20s %.3f秒", "总耗时:", totalTime.Seconds())
}

func preCreateDirs(rows [][]string, outputDir string) error {
	uniqueFolders := make(map[string]struct{})
	for i, row := range rows {
		if i == 0 || len(row) < 1 {
			continue
		}
		if col1 := sanitizeFileName(strings.TrimSpace(row[0])); col1 != "" {
			uniqueFolders[col1] = struct{}{}
		}
	}

	for folder := range uniqueFolders {
		dir := filepath.Join(outputDir, folder)
		if err := os.MkdirAll(dir, 0755); err != nil {
			return fmt.Errorf("创建目录失败(%s): %v", dir, err)
		}
	}
	return nil
}

func worker(id int, wg *sync.WaitGroup, rowChan <-chan []string, outputDir string) {
	defer wg.Done()
	client := &http.Client{Timeout: requestTimeout}

	for row := range rowChan {
		processRow(client, row, outputDir)
	}
}

func processRow(client *http.Client, row []string, outputDir string) {
	// 数据校验
	col1 := strings.TrimSpace(row[0])
	col2 := strings.TrimSpace(row[1])
	url := strings.TrimSpace(row[2])
	if col1 == "" || col2 == "" || url == "" {
		return
	}

	// URL校验
	if !strings.HasPrefix(strings.ToLower(url), "http") {
		return
	}

	// 下载文件
	data, contentType, err := downloadFile(client, url)
	if err != nil {
		return
	}

	// 生成保存路径
	dirPath := filepath.Join(outputDir, sanitizeFileName(col1))
	ext := getFileExtension(contentType, url)
	fileName := generateUniqueFilename(dirPath, sanitizeFileName(col2), ext)

	// 保存文件
	if saveFile(filepath.Join(dirPath, fileName), data) {
		atomic.AddInt64(&successCount, 1)
	}
}

func downloadFile(client *http.Client, url string) ([]byte, string, error) {
	resp, err := client.Get(url)
	if err != nil {
		return nil, "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, "", fmt.Errorf("状态码错误: %d", resp.StatusCode)
	}

	var buf bytes.Buffer
	if _, err := io.Copy(&buf, resp.Body); err != nil {
		return nil, "", err
	}

	return buf.Bytes(), resp.Header.Get("Content-Type"), nil
}

func saveFile(path string, data []byte) bool {
	if err := os.WriteFile(path, data, 0644); err != nil {
		return false
	}
	return true
}

func sanitizeFileName(name string) string {
	return strings.NewReplacer(
		"/", "_", "\\", "_", ":", "_", "*", "_",
		"?", "_", "\"", "_", "<", "_", ">", "_", "|", "_",
	).Replace(name)
}

func getFileExtension(contentType, url string) string {
	// 从ContentType获取扩展名
	if contentType != "" {
		parts := strings.SplitN(contentType, ";", 2)
		if ext, ok := mimeToExt[strings.TrimSpace(parts[0])]; ok {
			return ext
		}
	}

	// 从URL路径获取扩展名
	if ext := path.Ext(strings.Split(url, "?")[0]); ext != "" {
		return ext
	}

	return ".bin"
}

func generateUniqueFilename(dir, baseName, ext string) string {
	counter := 0
	var filename string

	for {
		if counter == 0 {
			filename = baseName + ext
		} else {
			filename = fmt.Sprintf("%s_%d%s", baseName, counter, ext)
		}

		fullPath := filepath.Join(dir, filename)
		if _, err := os.Stat(fullPath); os.IsNotExist(err) {
			break
		}
		counter++
	}

	return filename
}